/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.code.androidsmb;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import org.alfresco.jlan.app.XMLServerConfiguration;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.server.config.InvalidConfigurationException;
import org.alfresco.jlan.smb.server.CIFSConfigSection;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

interface MessageListener {
	
	public void message(String msg);
	
	public void error(String msg);
	
	public void error(String msg, Throwable e);
	
}

/**
 * This class does all the work for setting up and managing Bluetooth
 * connections with other devices. It has a thread that listens for
 * incoming connections, a thread for connecting with a device, and a
 * thread for performing data transmissions when connected.
 */
public class AndroidSMBService extends Service implements AndroidSMBConstants {
	
	final private String TAG="AndroidSMBService";

	private NotificationManager mNM;
	
	private int status = STOPPED;

	protected CifsServer server;
	
	private DebugHandler logHandler;
	
    /**
     * Class for clients to access.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with
     * IPC.
     */
    public class LocalBinder extends Binder {
    	AndroidSMBService getService() {
            return AndroidSMBService.this;
        }
    }
    
    /* (non-Javadoc)
     * @see android.app.Service#onCreate()
     * This will be called automatically when the Activity tries to bind to this service if it isn't already created.
     */
    @Override
    public void onCreate() {
    	Toast.makeText(this, "Android SMB Service Created", Toast.LENGTH_LONG).show();
    	// Display a notification about us starting.  We put an icon in the status bar.
    	this.logHandler = DebugHandler.getInstance();
    }

    public int getStatus(){
    	return status;
    }
    
    private int setStatus(int status){
    	int temp = this.getStatus();
    	this.status = status;
    	return temp;
    }
    
    public DebugHandler getLogHandler(){
    	return this.logHandler;
    }
    
    /* (non-Javadoc)
     * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
     * 
     * Even though this 'Service' is 'created' automatically, this won't get called until the activity explicitly calls 'startActivity'
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "Received start id " + startId + ": " + intent);
        Toast.makeText(this, R.string.smb_started, Toast.LENGTH_LONG).show();
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
    	mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
    	showNotification();
    	new Thread(new Runnable() {
		
			public void run() {
				try {
					InputStream stream = AndroidSMBService.this.getResources().openRawResource(R.raw.config);
			    	XMLServerConfiguration srvConfig = new XMLServerConfiguration();
					srvConfig.loadConfiguration(new InputStreamReader(stream));

					server = new CifsServer(srvConfig);
			    	AndroidSMBService.this.setStatus(RUNNING);
			    	AndroidSMBService.this.getLogHandler().publish(new LogRecord(Level.INFO,"SMB Started"));
			    	server.run();
				} catch (Exception e) {
					server.stop();
					AndroidSMBService.this.setStatus(STOPPED);
			    	Toast.makeText(AndroidSMBService.this, "Error with server", Toast.LENGTH_LONG).show();
			        e.printStackTrace();
					Log.e(TAG, "error loading server", e);
				}
			}
		}).start();
    	return START_STICKY;
    }

	@Override
    public void onDestroy() {
    	Toast.makeText(this, "Service Process destroyed", Toast.LENGTH_LONG).show();
    }
    
    public void shutDown() {
    	server.stop();
    	AndroidSMBService.this.getLogHandler().publish(new LogRecord(Level.INFO,"SMB Stopped"));
    	mNM.cancel(R.string.smb_service_label);
    	this.setStatus(STOPPED);
        // Tell the user we stopped.
        Toast.makeText(this, R.string.smb_stopped, Toast.LENGTH_LONG).show();
    }

    @Override
    public IBinder onBind(Intent intent) {
    	return mBinder;
    }

    @Override
	public boolean onUnbind(Intent intent) {
		// TODO Auto-generated method stub
    	this.logHandler = null;
		return super.onUnbind(intent);
	}

	// This is the object that receives interactions from clients.  See
    // RemoteService for a more complete example.
    private final IBinder mBinder = new LocalBinder();

    
	
    /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText(R.string.app_name);

        // Set the icon, scrolling text and timestamp
        Notification notification = new Notification(R.drawable.icon, text,
                System.currentTimeMillis());
        notification.flags |= Notification.FLAG_NO_CLEAR|Notification.FLAG_ONGOING_EVENT;

        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, AndroidSMB.class), 0);

        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, getText(R.string.smb_service_label),
                       text, contentIntent);

        // Send the notification.
        // We use a layout id because it is a unique number.  We use it later to cancel.
        mNM.notify(R.string.smb_service_label, notification);
    }


//    // Debugging
//    private static final String TAG = "BluetoothChatService";
//    private static final boolean D = true;
//
//    // Name for the SDP record when creating server socket
//    private static final String NAME = "BluetoothChat";
//
//    // Unique UUID for this application
//    private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
//
//    // Member fields
//    private final BluetoothAdapter mAdapter;
//    private final Handler mHandler;
//    private AcceptThread mAcceptThread;
//    private ConnectThread mConnectThread;
//    private ConnectedThread mConnectedThread;
//    private int mState;
//
//    // Constants that indicate the current connection state
//    public static final int STATE_NONE = 0;       // we're doing nothing
//    public static final int STATE_LISTEN = 1;     // now listening for incoming connections
//    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
//    public static final int STATE_CONNECTED = 3;  // now connected to a remote device
//
//    /**
//     * Constructor. Prepares a new BluetoothChat session.
//     * @param context  The UI Activity Context
//     * @param handler  A Handler to send messages back to the UI Activity
//     */
//    public AndroidSMBService(Context context, Handler handler) {
//        mAdapter = BluetoothAdapter.getDefaultAdapter();
//        mState = STATE_NONE;
//        mHandler = handler;
//    }
//
//    /**
//     * Set the current state of the chat connection
//     * @param state  An integer defining the current connection state
//     */
//    private synchronized void setState(int state) {
//        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
//        mState = state;
//
//        // Give the new state to the Handler so the UI Activity can update
//        mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
//    }
//
//    /**
//     * Return the current connection state. */
//    public synchronized int getState() {
//        return mState;
//    }
//
//    /**
//     * Start the chat service. Specifically start AcceptThread to begin a
//     * session in listening (server) mode. Called by the Activity onResume() */
//    public synchronized void start() {
//        if (D) Log.d(TAG, "start");
//
//        // Cancel any thread attempting to make a connection
//        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
//
//        // Cancel any thread currently running a connection
//        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
//
//        // Start the thread to listen on a BluetoothServerSocket
//        if (mAcceptThread == null) {
//            mAcceptThread = new AcceptThread();
//            mAcceptThread.start();
//        }
//        setState(STATE_LISTEN);
//    }
//
//    /**
//     * Start the ConnectThread to initiate a connection to a remote device.
//     * @param device  The BluetoothDevice to connect
//     */
//    public synchronized void connect(BluetoothDevice device) {
//        if (D) Log.d(TAG, "connect to: " + device);
//
//        // Cancel any thread attempting to make a connection
//        if (mState == STATE_CONNECTING) {
//            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
//        }
//
//        // Cancel any thread currently running a connection
//        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
//
//        // Start the thread to connect with the given device
//        mConnectThread = new ConnectThread(device);
//        mConnectThread.start();
//        setState(STATE_CONNECTING);
//    }
//
//    /**
//     * Start the ConnectedThread to begin managing a Bluetooth connection
//     * @param socket  The BluetoothSocket on which the connection was made
//     * @param device  The BluetoothDevice that has been connected
//     */
//    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
//        if (D) Log.d(TAG, "connected");
//
//        // Cancel the thread that completed the connection
//        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
//
//        // Cancel any thread currently running a connection
//        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
//
//        // Cancel the accept thread because we only want to connect to one device
//        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
//
//        // Start the thread to manage the connection and perform transmissions
//        mConnectedThread = new ConnectedThread(socket);
//        mConnectedThread.start();
//
//        // Send the name of the connected device back to the UI Activity
//        Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);
//        Bundle bundle = new Bundle();
//        bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());
//        msg.setData(bundle);
//        mHandler.sendMessage(msg);
//
//        setState(STATE_CONNECTED);
//    }
//
//    /**
//     * Stop all threads
//     */
//    public synchronized void stop() {
//        if (D) Log.d(TAG, "stop");
//        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
//        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
//        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
//        setState(STATE_NONE);
//    }
//
//    /**
//     * Write to the ConnectedThread in an unsynchronized manner
//     * @param out The bytes to write
//     * @see ConnectedThread#write(byte[])
//     */
//    public void write(byte[] out) {
//        // Create temporary object
//        ConnectedThread r;
//        // Synchronize a copy of the ConnectedThread
//        synchronized (this) {
//            if (mState != STATE_CONNECTED) return;
//            r = mConnectedThread;
//        }
//        // Perform the write unsynchronized
//        r.write(out);
//    }
//
//    /**
//     * Indicate that the connection attempt failed and notify the UI Activity.
//     */
//    private void connectionFailed() {
//        setState(STATE_LISTEN);
//
//        // Send a failure message back to the Activity
//        Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
//        Bundle bundle = new Bundle();
//        bundle.putString(BluetoothChat.TOAST, "Unable to connect device");
//        msg.setData(bundle);
//        mHandler.sendMessage(msg);
//    }
//
//    /**
//     * Indicate that the connection was lost and notify the UI Activity.
//     */
//    private void connectionLost() {
//        setState(STATE_LISTEN);
//
//        // Send a failure message back to the Activity
//        Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
//        Bundle bundle = new Bundle();
//        bundle.putString(BluetoothChat.TOAST, "Device connection was lost");
//        msg.setData(bundle);
//        mHandler.sendMessage(msg);
//    }
//
//    /**
//     * This thread runs while listening for incoming connections. It behaves
//     * like a server-side client. It runs until a connection is accepted
//     * (or until cancelled).
//     */
//    private class AcceptThread extends Thread {
//        // The local server socket
//        private final BluetoothServerSocket mmServerSocket;
//
//        public AcceptThread() {
//            BluetoothServerSocket tmp = null;
//
//            // Create a new listening server socket
//            try {
//                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
//            } catch (IOException e) {
//                Log.e(TAG, "listen() failed", e);
//            }
//            mmServerSocket = tmp;
//        }
//
//        public void run() {
//            if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
//            setName("AcceptThread");
//            BluetoothSocket socket = null;
//
//            // Listen to the server socket if we're not connected
//            while (mState != STATE_CONNECTED) {
//                try {
//                    // This is a blocking call and will only return on a
//                    // successful connection or an exception
//                    socket = mmServerSocket.accept();
//                } catch (IOException e) {
//                    Log.e(TAG, "accept() failed", e);
//                    break;
//                }
//
//                // If a connection was accepted
//                if (socket != null) {
//                    synchronized (AndroidSMBService.this) {
//                        switch (mState) {
//                        case STATE_LISTEN:
//                        case STATE_CONNECTING:
//                            // Situation normal. Start the connected thread.
//                            connected(socket, socket.getRemoteDevice());
//                            break;
//                        case STATE_NONE:
//                        case STATE_CONNECTED:
//                            // Either not ready or already connected. Terminate new socket.
//                            try {
//                                socket.close();
//                            } catch (IOException e) {
//                                Log.e(TAG, "Could not close unwanted socket", e);
//                            }
//                            break;
//                        }
//                    }
//                }
//            }
//            if (D) Log.i(TAG, "END mAcceptThread");
//        }
//
//        public void cancel() {
//            if (D) Log.d(TAG, "cancel " + this);
//            try {
//                mmServerSocket.close();
//            } catch (IOException e) {
//                Log.e(TAG, "close() of server failed", e);
//            }
//        }
//    }
//
//
//    /**
//     * This thread runs while attempting to make an outgoing connection
//     * with a device. It runs straight through; the connection either
//     * succeeds or fails.
//     */
//    private class ConnectThread extends Thread {
//        private final BluetoothSocket mmSocket;
//        private final BluetoothDevice mmDevice;
//
//        public ConnectThread(BluetoothDevice device) {
//            mmDevice = device;
//            BluetoothSocket tmp = null;
//
//            // Get a BluetoothSocket for a connection with the
//            // given BluetoothDevice
//            try {
//                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
//            } catch (IOException e) {
//                Log.e(TAG, "create() failed", e);
//            }
//            mmSocket = tmp;
//        }
//
//        public void run() {
//            Log.i(TAG, "BEGIN mConnectThread");
//            setName("ConnectThread");
//
//            // Always cancel discovery because it will slow down a connection
//            mAdapter.cancelDiscovery();
//
//            // Make a connection to the BluetoothSocket
//            try {
//                // This is a blocking call and will only return on a
//                // successful connection or an exception
//                mmSocket.connect();
//            } catch (IOException e) {
//                connectionFailed();
//                // Close the socket
//                try {
//                    mmSocket.close();
//                } catch (IOException e2) {
//                    Log.e(TAG, "unable to close() socket during connection failure", e2);
//                }
//                // Start the service over to restart listening mode
//                AndroidSMBService.this.start();
//                return;
//            }
//
//            // Reset the ConnectThread because we're done
//            synchronized (AndroidSMBService.this) {
//                mConnectThread = null;
//            }
//
//            // Start the connected thread
//            connected(mmSocket, mmDevice);
//        }
//
//        public void cancel() {
//            try {
//                mmSocket.close();
//            } catch (IOException e) {
//                Log.e(TAG, "close() of connect socket failed", e);
//            }
//        }
//    }
//
//    /**
//     * This thread runs during a connection with a remote device.
//     * It handles all incoming and outgoing transmissions.
//     */
//    private class ConnectedThread extends Thread {
//        private final BluetoothSocket mmSocket;
//        private final InputStream mmInStream;
//        private final OutputStream mmOutStream;
//
//        public ConnectedThread(BluetoothSocket socket) {
//            Log.d(TAG, "create ConnectedThread");
//            mmSocket = socket;
//            InputStream tmpIn = null;
//            OutputStream tmpOut = null;
//
//            // Get the BluetoothSocket input and output streams
//            try {
//                tmpIn = socket.getInputStream();
//                tmpOut = socket.getOutputStream();
//            } catch (IOException e) {
//                Log.e(TAG, "temp sockets not created", e);
//            }
//
//            mmInStream = tmpIn;
//            mmOutStream = tmpOut;
//        }
//
//        public void run() {
//            Log.i(TAG, "BEGIN mConnectedThread");
//            byte[] buffer = new byte[1024];
//            int bytes;
//
//            // Keep listening to the InputStream while connected
//            while (true) {
//                try {
//                    // Read from the InputStream
//                    bytes = mmInStream.read(buffer);
//
//                    // Send the obtained bytes to the UI Activity
//                    mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
//                            .sendToTarget();
//                } catch (IOException e) {
//                    Log.e(TAG, "disconnected", e);
//                    connectionLost();
//                    break;
//                }
//            }
//        }
//
//        /**
//         * Write to the connected OutStream.
//         * @param buffer  The bytes to write
//         */
//        public void write(byte[] buffer) {
//            try {
//                mmOutStream.write(buffer);
//
//                // Share the sent message back to the UI Activity
//                mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
//                        .sendToTarget();
//            } catch (IOException e) {
//                Log.e(TAG, "Exception during write", e);
//            }
//        }
//
//        public void cancel() {
//            try {
//                mmSocket.close();
//            } catch (IOException e) {
//                Log.e(TAG, "close() of connect socket failed", e);
//            }
//        }
//    }
}
